Skip to content

refactor: add IAsyncDisposable to async-managing types#139

Merged
mivertowski merged 1 commit intomainfrom
refactor/add-async-disposable
Apr 21, 2026
Merged

refactor: add IAsyncDisposable to async-managing types#139
mivertowski merged 1 commit intomainfrom
refactor/add-async-disposable

Conversation

@mivertowski
Copy link
Copy Markdown
Owner

Summary

Upgrades 16 types that manage async resources (background tasks, semaphores, channels, CTS, GPU handles) to implement IAsyncDisposable alongside IDisposable. Each now has a cooperative async shutdown path that awaits in-flight work instead of blocking on .GetAwaiter().GetResult() or Task.Wait().

  • Build: 0 errors, 0 warnings (dotnet build DotCompute.sln -c Release).
  • Tests: all affected unit suites pass: Core 486/486, Plugins Sandbox 14/14, Core Security 101/101, Abstractions 93/93, CPU 25/25. Two pre-existing flaky failures observed on main (RingKernelWatchdogTests.Dispose_CleansUpResources, TimestampInjectorTests.InjectTimestampIntoPtx_InjectedCode_MaintainsValidPtxSyntax) are unaffected.

Types upgraded (with heuristic trigger)

Type Path Reason
BackpressureManager src/Extensions/DotCompute.Linq/Reactive/BackpressureManager.cs Owns CTS; block-strategy producer park
LogBuffer src/Core/DotCompute.Core/Logging/LogBuffer.cs Sync-over-async + background Task + Channel drain
StructuredLogger src/Core/DotCompute.Core/Logging/StructuredLogger.cs Sync-over-async on buffer flush
MemorySanitizer src/Core/DotCompute.Core/Security/MemorySanitizer.cs Sync-over-async on per-allocation secure wipe
MemoryProtection src/Core/DotCompute.Core/Security/MemoryProtection.cs Sync-over-async on per-region secure wipe
CudaPerformanceProfiler src/Backends/DotCompute.Backends.CUDA/Profiling/CudaPerformanceProfiler.cs Sync-over-async on StopProfilingAsync
CudaPersistentKernelManager src/Backends/DotCompute.Backends.CUDA/Persistent/CudaPersistentKernelManager.cs Sync-over-async on StopKernelAsync for each active kernel
IPersistentKernelHandle / PersistentKernelHandle same file Sync-over-async on StopAsync; interface extended
MetalBackend src/Backends/DotCompute.Backends.Metal/MetalBackend.cs AsTask().GetAwaiter().GetResult() on each accelerator
RuntimeExecutor src/Extensions/DotCompute.Linq/CodeGeneration/RuntimeExecutor.cs AsTask().Wait() on accelerator dispose
CudaRuntimeKernelCompiler src/Extensions/DotCompute.Linq/Compilation/CudaRuntimeKernelCompiler.cs SemaphoreSlim.Wait() in sync Dispose
MetalRuntimeKernelCompiler src/Extensions/DotCompute.Linq/Compilation/MetalRuntimeKernelCompiler.cs SemaphoreSlim.Wait() in sync Dispose
CompilationPipeline src/Extensions/DotCompute.Linq/CodeGeneration/CompilationPipeline.cs Owns both async-disposable GPU compilers
NuGetPluginManager src/Runtime/DotCompute.Plugins/Managers/NuGetPluginManager.cs Sync-over-async on UnloadPluginAsync
SandboxedPlugin src/Runtime/DotCompute.Plugins/Security/SandboxedPlugin.cs Sync-over-async on TerminateAsync
PluginSandbox src/Runtime/DotCompute.Plugins/Security/PluginSandbox.cs Sync-over-async across all sandboxed plugins
KernelSandbox src/Extensions/DotCompute.Algorithms/Security/KernelSandbox.cs Task.WaitAll(30s) on sandbox destruction tasks

Deliberately skipped

  • PipelineTelemetryCollector, NumaScheduler, MetalGraphExecutor, MetalEventManager, CudaEventManager, MultiGpuSynchronizer, ParallelOptimizations.WorkStealingPool, PluginLifecycleManager, AlgorithmPluginLifecycle — sync-only Dispose (CTS cancel + timer dispose, Thread.Join, or trivial handle release). No awaitable in-flight work to drain; over-async would be churn.
  • CudaMemoryManager, MetalMemoryManager — already implement IAsyncDisposable via BaseMemoryManager.
  • CudaRingKernelRuntime — already implements IAsyncDisposable via IRingKernelRuntime.

Call-site migrations

4 test methods that called sync Dispose() inside async Task methods were upgraded to await ...DisposeAsync():

  • tests/Unit/DotCompute.Core.Tests/Security/MemorySanitizerTests.cs — 2 cases
  • tests/Unit/DotCompute.Core.Tests/Security/MemoryProtectionTests.cs — 1 case
  • tests/Unit/DotCompute.Plugins.Tests/Security/PluginSandboxTests.cs — 2 cases

Production call sites were left as-is: the build-time CA1849/VSTHRD103 analyzers would have flagged any regression (and the build is warning-clean).

Pattern applied

Each upgraded class keeps both sync and async paths idempotent and shares cleanup where reasonable:

public async ValueTask DisposeAsync()
{
    if (_disposed) return;
    _disposed = true;

    // Cancel CTS cooperatively, await background tasks,
    // drain channels, await per-resource AsyncDispose.
    await BackgroundWorkAsync().ConfigureAwait(false);

    // Then release sync-only handles.
    _timer?.Dispose();
    _semaphore?.Dispose();
    _cts.Dispose();

    GC.SuppressFinalize(this);
}

No new interfaces were introduced — all additions go through System.IAsyncDisposable.

Test plan

  • dotnet build DotCompute.sln -c Release → 0 errors, 0 warnings
  • dotnet test on DotCompute.Core.Tests (486/486), DotCompute.Backends.CPU.Tests (25/25), DotCompute.Abstractions.Tests (93/93), DotCompute.Plugins.Tests filter PluginSandboxTests (14/14), DotCompute.Core.Tests filter Memory{Sanitizer,Protection}Tests (101/101)
  • Parity check against main — no new test failures introduced by this PR
  • Hardware tests (CUDA) — reviewer to run on CUDA-enabled CI; unaffected by this PR since no kernel semantics changed
  • Downstream consumers (Orleans.GpuBridge.Core) — review whether any using blocks on these types should migrate to await using

🤖 Generated with Claude Code

Upgrades 16 types that manage async resources (background tasks,
semaphores, channels, CTS, GPU handles) to support cooperative async
disposal. Callers in async contexts now have a clean shutdown path
that does not block on `.GetAwaiter().GetResult()` or `Task.Wait()`.

Each upgraded type gets a `DisposeAsync` that awaits pending work
before releasing resources, while its sync `Dispose` is preserved
for `using` blocks and non-async callers.

Types upgraded (by heuristic criteria):

Core:
  - LogBuffer                 (sync-over-async + Channel drain)
  - StructuredLogger          (sync-over-async on log buffer flush)
  - MemorySanitizer           (sync-over-async on secure wipe)
  - MemoryProtection          (sync-over-async on secure wipe)

LINQ:
  - BackpressureManager       (CTS + producer unblock)
  - RuntimeExecutor           (AsTask().Wait() on accelerator dispose)
  - CudaRuntimeKernelCompiler (SemaphoreSlim.Wait() in Dispose)
  - MetalRuntimeKernelCompiler(SemaphoreSlim.Wait() in Dispose)
  - CompilationPipeline       (cascades GPU compiler disposal)

CUDA:
  - CudaPerformanceProfiler   (sync-over-async on StopProfilingAsync)
  - CudaPersistentKernelManager (sync-over-async on StopKernelAsync)
  - IPersistentKernelHandle   (interface extended with IAsyncDisposable)
  - PersistentKernelHandle    (awaits StopAsync)

Metal:
  - MetalBackend              (AsTask().GetAwaiter().GetResult() on
                               accelerator.DisposeAsync)

Plugins/Security:
  - NuGetPluginManager        (sync-over-async on UnloadPluginAsync)
  - SandboxedPlugin           (sync-over-async on TerminateAsync)
  - PluginSandbox             (sync-over-async on plugin termination)

Algorithms:
  - KernelSandbox             (Task.WaitAll(30s) on sandbox destroy)

Deliberately skipped:
  - PipelineTelemetryCollector, NumaScheduler, MetalGraphExecutor,
    MetalEventManager, CudaEventManager, MultiGpuSynchronizer,
    ParallelOptimizations.WorkStealingPool, PluginLifecycleManager,
    AlgorithmPluginLifecycle — their Dispose is purely synchronous
    (CTS + Timer, thread.Join, or trivial handle release) with no
    awaitable work to drain.
  - CudaMemoryManager, MetalMemoryManager — already inherit
    IAsyncDisposable from BaseMemoryManager.
  - CudaRingKernelRuntime — already implements IAsyncDisposable via
    IRingKernelRuntime.

Test call-site migrations (sync `Dispose()` inside async test
methods → `await ...DisposeAsync()`): 4 test methods across
MemorySanitizerTests, MemoryProtectionTests, and PluginSandboxTests.

Build: 0 errors, 0 warnings (Release).
Tests: all affected unit suites pass (Core 486/486, Plugins Sandbox
14/14, Core Security 101/101). Two pre-existing flaky failures on
main (`RingKernelWatchdogTests.Dispose_CleansUpResources`,
`TimestampInjectorTests.InjectTimestampIntoPtx_...`) are unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@mivertowski mivertowski merged commit 27cf533 into main Apr 21, 2026
7 checks passed
@mivertowski mivertowski deleted the refactor/add-async-disposable branch April 21, 2026 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant